using System;
using SharpMud.Net.Telnet.IAC;

namespace SharpMud.Net.Telnet.Session.NegotationAgents
{
	/// <summary>
	/// This is the negotiation daemon for determining the client terminal type.
	/// </summary>
	public class TerminalType : IFeatureNegotiationAgent
	{
		#region Private Variables
		private TelnetSessionBase								_HostSession;
		private System.Collections.Specialized.ListDictionary	_TerminalTypes;
		private bool											_IsListCompleted;
		private bool											_IsTerminalTypeLocked;
		private int												_LastItemRepeatCount;
		private string											_CurrentTerminalType;

		private bool											_IsNegotiationRefused;

		private bool _Enabled;
		private int _ChangesPending;
		private bool _RequireClientVerification;
		
		private FeatureNegotiationBehavior _Behavior;
		#endregion

		#region Constructors
		private TerminalType()
		{
		}
		public TerminalType(TelnetSessionBase hostSession)
		{
			_Behavior = (FeatureNegotiationBehavior)(-1);

			_Enabled = false;
			_ChangesPending = 0;
			_HostSession = hostSession;
			_RequireClientVerification = true;
			_IsListCompleted = false;
			_IsTerminalTypeLocked = false;
			_LastItemRepeatCount=0;
			_TerminalTypes = new System.Collections.Specialized.ListDictionary(new System.Collections.CaseInsensitiveComparer() );
			hostSession.IAC.In.IAC_Feature_Negotiation +=new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventHandler(IAC_Feature_Negotiation);
			hostSession.IAC.In.IAC_Subrequest +=new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventHandler(IAC_Subrequest);
		}

		public bool ChangePending
		{
			get
			{
				return (_ChangesPending>0);
			}
		}
		#endregion

		public void RequestNextTTYPE()
		{
			if(_Enabled==false)
			{
				throw new InvalidOperationException("Cannot request TerminalType from Telnet client when the TerminalType agent is not Enabled.");
			}
			this._HostSession.IAC.Out.Send_Subrequest_Packet(ControlCodes.TELOPT.TTYPE,new byte[] {(byte)ControlCodes.SEND});
		}

		#region Event Handlers
		private void IAC_Feature_Negotiation(SharpMud.Net.Telnet.Session.DataProcessors.IAC.InputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventArgs e)
		{
			if((byte)e.FeatureType!=(byte)ControlCodes.TELOPT.TTYPE)
			{
				return;
			}
			else
			{
			
				if(e.NegotiationQuery == ControlCodes.DO)
				{
					//The client is saying it wants us to accept its TerminalType.
					//Servers don't do that. Tell it NO! Bad client!
					_HostSession.IAC.Out.Send_FeatureNegotiation_Packet(ControlCodes.DONT,ControlCodes.TELOPT.TTYPE);
					return;
				}
				if(e.NegotiationQuery == ControlCodes.DONT)
				{
					//The client is saying it doesn't want us to accept its TerminalType.
					//Which is okay, since it isn't supported server side.
					return;
				}
				if(e.NegotiationQuery == ControlCodes.WILL)
				{
					if(!_Enabled)
						_Enabled = true;
					if(ChangePending)
						_ChangesPending--;
					_IsNegotiationRefused = false;
					this.RequestNextTTYPE();
					return;
				}
				if(e.NegotiationQuery == ControlCodes.WONT)
				{
					if(_Enabled)
						_Enabled = false;
					if(ChangePending)
						_ChangesPending--;
					_IsNegotiationRefused = true;
					return;
				}
			}
		}

		private void IAC_Subrequest(SharpMud.Net.Telnet.Session.DataProcessors.IAC.InputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventArgs e)
		{
			if(_IsTerminalTypeLocked) return;

			if(e.FeatureType!=ControlCodes.TELOPT.TTYPE)
				return;
			if(e.SubrequestData[0]!=(byte)ControlCodes.IS)
				return;
			string ttype = System.Text.Encoding.ASCII.GetString(e.SubrequestData,1,e.SubrequestData.Length-1);
			if(!_TerminalTypes.Contains(ttype))
			{
				//Its a new terminal type! Register it.
				this._TerminalTypes.Add(ttype,ttype);
				_IsListCompleted = false;
				if(this.NewTermnalTypeDetected!=null)
					this.NewTermnalTypeDetected(this,System.EventArgs.Empty);
			}
			else
			{
				//We already have this terminal type. The list has been exhausted.
				_IsListCompleted = true;
			}
			if(this.CurrentTerminalType.ToLower().Trim()!=ttype.ToLower().Trim())
			{
				this._CurrentTerminalType = ttype;
				_LastItemRepeatCount = 0;
				if(this.TermnalTypeChanged!=null)
					this.TermnalTypeChanged(this,System.EventArgs.Empty);
			}
			else
			{
				//We were already on this mode!
				//1 repeat means end of list
				//2 repeats (or more) means we are locked on what we have
				this._LastItemRepeatCount++;
				if(_LastItemRepeatCount==1)
				{
					this._IsListCompleted = true;
					if(ListCompleted!=null)
						this.ListCompleted(this,System.EventArgs.Empty);
				}
				else if(_LastItemRepeatCount>1)
				{
					this._IsTerminalTypeLocked = true;
					if(TerminalTypeLocked!=null)
						this.TerminalTypeLocked(this,System.EventArgs.Empty);
				}
			}
		}

		#endregion

		#region Events
		public event System.EventHandler NewTermnalTypeDetected;
		public event System.EventHandler TermnalTypeChanged;
		public event System.EventHandler ListCompleted;
		public event System.EventHandler TerminalTypeLocked;
		#endregion
		
		#region Properties
		public string CurrentTerminalType
		{
			get
			{
				return this._CurrentTerminalType;
			}
		}

		public bool IsTerminalTypeLocked
		{
			get
			{
				return this._IsTerminalTypeLocked;
			}
		}

		public bool IsListCompleted
		{
			get
			{
				return this._IsListCompleted;
			}
		}
		public bool IsNegotiationRefused
		{
			get
			{
				return this._IsNegotiationRefused;
			}
		}
		#endregion

		#region IFeatureNegotiationAgent Members

		public SharpMud.Net.Telnet.Session.FeatureNegotiationBehavior Behavior
		{
			get
			{
				return _Behavior;
			}
			set
			{
				if(value!=_Behavior)
				{
					if(value==FeatureNegotiationBehavior.DemandClientSupport)
					{
						this.TurnFeatureOn();
					}
					_Behavior = value;
				}
			}
		}

		public System.TriState Status
		{
			get
			{
				if(_IsNegotiationRefused)
					return TriState.False;
				if(this._RequireClientVerification && this._ChangesPending>0)
					return TriState.UseDefault;
				if(this._Enabled)
					return TriState.True;
				else
					return TriState.False;
			}
		}

		public bool EffectiveStatus
		{
			get
			{
				return this._Enabled;
			}
		}

		public void TurnFeatureOn()
		{
			if(_Enabled==false && (ChangePending==false || _RequireClientVerification==false))
			{
				//Need to turn remote TerminalType support back on
				_ChangesPending++;
				this._HostSession.IAC.Out.Send_FeatureNegotiation_Packet(ControlCodes.DO,ControlCodes.TELOPT.TTYPE);
				if(!_RequireClientVerification)
				{
					_Enabled = true;
				}
				return;
			}
		}

		public void TurnFeatureOff()
		{
			if(_Enabled==true && (ChangePending==false || _RequireClientVerification==false))
			{
				if(this.IsNegotiationRefused)
				{
					throw new InvalidOperationException("You cannot Enable a TerminalType agent when the Telnet client has already refused TerminalType negotiation.");
				}
				//Need to turn TerminalType support off
				_ChangesPending++;
				this._HostSession.IAC.Out.Send_FeatureNegotiation_Packet(ControlCodes.DONT,ControlCodes.TELOPT.TTYPE);
				if(!_RequireClientVerification)
				{
					_Enabled = false;
				}
				return;
			}
		}

		#endregion
	}
}